package tw.com.hitevision.whiteboard.android.widgets.floatView

import android.app.Activity
import android.app.Application
import android.content.Context
import android.view.View
import tw.com.hitevision.whiteboard.android.widgets.floatView.interfaces.OnDisplayHeight
import tw.com.hitevision.whiteboard.android.widgets.floatView.interfaces.OnInvokeView
import tw.com.hitevision.whiteboard.android.widgets.floatView.data.FloatConfig
import tw.com.hitevision.whiteboard.android.widgets.floatView.enums.ShowPattern
import tw.com.hitevision.whiteboard.android.widgets.floatView.enums.SidePattern
import tw.com.hitevision.whiteboard.android.widgets.floatView.interfaces.*
import tw.com.hitevision.whiteboard.android.widgets.floatView.permission.PermissionUtils
import tw.com.hitevision.whiteboard.android.widgets.floatView.utils.LifecycleUtils
import tw.com.hitevision.whiteboard.android.widgets.floatView.utils.Logger
import tw.com.hitevision.whiteboard.android.widgets.floatView.widget.activityfloat.ActivityFloatManager
import tw.com.hitevision.whiteboard.android.widgets.floatView.widget.appfloat.FloatManager
import java.lang.ref.WeakReference

class FloatingView {

    companion object {
        internal var isDebug = false
        private var activityWr: WeakReference<Activity>? = null
        private var isInitialized = false

        @JvmStatic
        @JvmOverloads
        fun init(application: Application, isDebug: Boolean = false) {
            Companion.isDebug = isDebug
            isInitialized = true
            LifecycleUtils.setLifecycleCallbacks(application)
        }

        @JvmStatic
        fun with(activity: Context): Builder {
            if (activity is Activity) activityWr = WeakReference(activity)
            return Builder(activity)
        }

        // reference ActivityFloatManager
        @JvmStatic
        @JvmOverloads
        fun dismiss(activity: Activity? = null, tag: String? = null) =
            manager(activity)?.dismiss(tag)

        @JvmStatic
        @JvmOverloads
        fun hide(activity: Activity? = null, tag: String? = null) =
            manager(activity)?.setVisibility(tag, View.GONE)

        @JvmStatic
        @JvmOverloads
        fun show(activity: Activity? = null, tag: String? = null) =
            manager(activity)?.setVisibility(tag, View.VISIBLE)

        @JvmStatic
        @JvmOverloads
        fun setDragEnable(activity: Activity? = null, dragEnable: Boolean, tag: String? = null) =
            manager(activity)?.setDragEnable(dragEnable, tag)

        @JvmStatic
        @JvmOverloads
        fun isShow(activity: Activity? = null, tag: String? = null) = manager(activity)?.isShow(tag)

        @JvmStatic
        @JvmOverloads
        fun getFloatView(activity: Activity? = null, tag: String? = null) =
            manager(activity)?.getFloatView(tag)

        @JvmStatic
        fun getFloatManager(activity: Activity): ActivityFloatManager? {
            return manager(activity)
        }

        private fun manager(activity: Activity?) =
            (activity ?: activityWr?.get())?.run { ActivityFloatManager(this) }

        /**
         *  close system floating view
         */
        @JvmStatic
        @JvmOverloads
        fun dismissAppFloat(tag: String? = null) = FloatManager.dismiss(tag)

        /**
         *  hide system floating view
         */
        @JvmStatic
        @JvmOverloads
        fun hideAppFloat(tag: String? = null) = FloatManager.visible(false, tag, false)

        /**
         *  show system floating view
         */
        @JvmStatic
        @JvmOverloads
        fun showAppFloat(tag: String? = null) = FloatManager.visible(true, tag, true)

        /**
         *  set system floating view drag enable
         */
        @JvmStatic
        @JvmOverloads
        fun appFloatDragEnable(dragEnable: Boolean, tag: String? = null) =
            getConfig(tag).let { it?.dragEnable = dragEnable }

        /**
         *  get system floating view is show
         */
        @JvmStatic
        @JvmOverloads
        fun appFloatIsShow(tag: String? = null) = getConfig(tag) != null && getConfig(tag)!!.isShow

        /**
         *  get system floating view
         */
        @JvmStatic
        @JvmOverloads
        fun getAppFloatView(tag: String? = null): View? = getConfig(tag)?.layoutView

        /**
         *  add and remove filter for system floating view
         */
        @JvmStatic
        @JvmOverloads
        fun filterActivity(activity: Activity, tag: String? = null) =
            getFilterSet(tag)?.add(activity.componentName.className)

        @JvmStatic
        @JvmOverloads
        fun filterActivities(tag: String? = null, vararg clazz: Class<*>) =
            getFilterSet(tag)?.addAll(clazz.map { it.name })

        @JvmStatic
        @JvmOverloads
        fun removeFilter(activity: Activity, tag: String? = null) =
            getFilterSet(tag)?.remove(activity.componentName.className)

        @JvmStatic
        @JvmOverloads
        fun removeFilters(tag: String? = null, vararg clazz: Class<*>) =
            getFilterSet(tag)?.removeAll(clazz.map { it.name })

        @JvmStatic
        @JvmOverloads
        fun clearFilters(tag: String? = null) = getFilterSet(tag)?.clear()

        /**
         *  get system floating view config
         */
        private fun getConfig(tag: String?) = FloatManager.getAppFloatManager(tag)?.config

        private fun getFilterSet(tag: String?) = getConfig(tag)?.filterSet
    }


    /**
     *  floating view construction
     */
    class Builder(private val activity: Context) : OnPermissionResult {

        private val config = FloatConfig()

        fun setSidePattern(sidePattern: SidePattern) = apply { config.sidePattern = sidePattern }

        fun setShowPattern(showPattern: ShowPattern) = apply { config.showPattern = showPattern }

        @JvmOverloads
        fun setLayout(layoutId: Int, invokeView: OnInvokeView? = null) = apply {
            config.layoutId = layoutId
            config.invokeView = invokeView
        }

        @JvmOverloads
        fun setGravity(gravity: Int, offsetX: Int = 0, offsetY: Int = 0) = apply {
            config.gravity = gravity
            config.offsetPair = Pair(offsetX, offsetY)
        }

        fun setLocation(x: Int, y: Int) = apply { config.locationPair = Pair(x, y) }

        fun setTag(floatTag: String?) = apply { config.floatTag = floatTag }

        fun setDragEnable(dragEnable: Boolean) = apply { config.dragEnable = dragEnable }

        fun hasEditText(hasEditText: Boolean) = apply { config.hasEditText = hasEditText }

        @Deprecated("setLayout")
        fun invokeView(invokeView: OnInvokeView) = apply { config.invokeView = invokeView }

        /**
         *  register callback
         */
        fun registerCallbacks(callbacks: OnFloatCallbacks) = apply { config.callbacks = callbacks }

        fun registerCallback(builder: FloatCallbacks.Builder.() -> Unit) =
            apply { config.floatCallbacks = FloatCallbacks().apply { registerListener(builder) } }

        fun setAnimator(floatAnimator: OnFloatAnimator?) =
            apply { config.floatAnimator = floatAnimator }

        fun setAppFloatAnimator(appFloatAnimator: OnAppFloatAnimator?) =
            apply { config.appFloatAnimator = appFloatAnimator }

        /**
         *  set display view height
         */
        fun setDisplayHeight(displayHeight: OnDisplayHeight) =
            apply { config.displayHeight = displayHeight }

        fun setMatchParent(widthMatch: Boolean = false, heightMatch: Boolean = false) = apply {
            config.widthMatch = widthMatch
            config.heightMatch = heightMatch
        }

        /**
         *  set filter only for system floating view
         */
        fun setFilter(vararg clazz: Class<*>) = apply {
            clazz.forEach {
                config.filterSet.add(it.name)
                if (activity is Activity) {
                    if (it.name == activity.componentName.className) config.filterSelf = true
                }
            }
        }

        /**
         *  show floating view
         */
        fun show() = when {

            config.layoutId == null -> callbackCreateFailed(WARN_NO_LAYOUT)

            checkUninitialized() -> callbackCreateFailed(WARN_UNINITIALIZED)

            config.showPattern == ShowPattern.CURRENT_ACTIVITY -> createActivityFloat()

            PermissionUtils.checkPermission(activity) -> createAppFloat()

            else -> requestPermission()
        }

        /**
         *  check uninitialized
         */
        private fun checkUninitialized() = when (config.showPattern) {
            ShowPattern.CURRENT_ACTIVITY -> false
            ShowPattern.FOREGROUND, ShowPattern.BACKGROUND -> !isInitialized
            ShowPattern.ALL_TIME -> config.filterSet.isNotEmpty() && !isInitialized
        }

        /**
         *  create activity floating view
         */
        private fun createActivityFloat() =
            if (activity is Activity) ActivityFloatManager(activity).createFloat(config)
            else callbackCreateFailed(WARN_CONTEXT_ACTIVITY)

        /**
         *  create system floating view
         */
        private fun createAppFloat() = FloatManager.create(activity, config)

        /**
         *  request permission
         */
        private fun requestPermission() =
            if (activity is Activity) PermissionUtils.requestPermission(activity, this)
            else callbackCreateFailed(WARN_CONTEXT_REQUEST)

        /**
         *  request permission callback
         */
        override fun permissionResult(isOpen: Boolean) =
            if (isOpen) createAppFloat() else callbackCreateFailed(WARN_PERMISSION)

        /**
         *  callback create failed
         */
        private fun callbackCreateFailed(reason: String) {
            config.callbacks?.createdResult(false, reason, null)
            config.floatCallbacks?.builder?.createdResult?.invoke(false, reason, null)
            Logger.w(reason)
            if (reason == WARN_NO_LAYOUT || reason == WARN_UNINITIALIZED || reason == WARN_CONTEXT_ACTIVITY) {
                throw Exception(reason)
            }
        }
    }

}